/**
 * 
 */
package org.swing.utility.jai.imgio;

import java.awt.*;
import java.awt.color.ColorSpace;
import java.awt.color.ICC_ColorSpace;
import java.awt.color.ICC_Profile;
import java.awt.image.*;
import java.io.Serializable;

import com.sun.image.codec.jpeg.JPEGCodec;
import com.sun.image.codec.jpeg.JPEGEncodeParam;
import com.sun.image.codec.jpeg.JPEGImageEncoder;

import java.awt.Container;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.MediaTracker;
import java.awt.RenderingHints;
import java.awt.Toolkit;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;

import javax.swing.ImageIcon;

/**
 * @author Quynh Nhu
 * 
 */
public class ImageUtil implements Serializable {
	private ImageUtil() {
	}

	/**
	 * Return your resized image after applying the arguments invoked. The
	 * method resizes your image with constrain proportions, it takes in
	 * consider if the image is: Horizontal: Applies the 'fixedSide' argument as
	 * the new width, and calculates the new height in order to maintain the
	 * image scale. Vertical: Applies the 'fixedSide' argument as the new
	 * height, and calculates the new width. Squared: When image's width matches
	 * the height, the 'fixedSide' is used for both width and height.
	 * 
	 * @param originalImage
	 *            Your image as array of bytes, mostly retrieved from file
	 *            uploading.
	 * @param fixedSide
	 *            The side length in pixels which image will be resized
	 *            according to (ex. 240 = 240px). If you want to keep the image
	 *            original dimensions invoke fixedSide = 0.
	 * @param quality
	 *            The percentage of the newly created image's quality compared
	 *            to the original one. The number should be between 0 and 100,
	 *            passing 100 maintain the image's original quality.
	 * @throws InterruptedException
	 *             If something fails in MediaTracker#waitForID
	 * @throws IOException
	 *             If something fails in JPEGImageEncoder#encode or
	 *             ByteArrayOutputStream#close
	 */
	public byte[] resizeImage(byte[] originalImage, int fixedSide, int quality)
			throws InterruptedException, IOException {

		// This 'Image' instance will help in retrieving image dimensions, and
		// will hold modifications passed from 'BufferedImage'
		Image image = Toolkit.getDefaultToolkit().createImage(originalImage);
		MediaTracker mediaTracker = new MediaTracker(new Container());
		// Load and track 'image'
		mediaTracker.addImage(image, 1);
		mediaTracker.waitForID(1);
		BufferedImage bufferedImage = null;

		// Making sure arguments are passed as expected
		if (originalImage == null || originalImage.length == 0 || fixedSide < 0
				|| quality < 0 || quality > 100)
			throw new IllegalArgumentException(
					"Make sure your originalImage is not null, fixedSide is more than zero, and quality is between 0 and 100.");

		// Keep the image original dimensions
		if (fixedSide == 0) {
			bufferedImage = new BufferedImage(image.getWidth(null),
					image.getHeight(null), BufferedImage.TYPE_INT_RGB);
			Graphics2D graphics2D = bufferedImage.createGraphics();
			graphics2D.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
					RenderingHints.VALUE_INTERPOLATION_BILINEAR);
			graphics2D.drawImage(image, 0, 0, image.getWidth(null),
					image.getHeight(null), null);
		}

		// If your invoked image is squared
		else if (image.getHeight(null) == image.getWidth(null)) {
			// Use 'fixedSide' value for both width, and height
			bufferedImage = new BufferedImage(fixedSide, fixedSide,
					BufferedImage.TYPE_INT_RGB);
			Graphics2D graphics2D = bufferedImage.createGraphics();
			graphics2D.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
					RenderingHints.VALUE_INTERPOLATION_BILINEAR);
			graphics2D.drawImage(image, 0, 0, fixedSide, fixedSide, null);

		}
		// If your invoked image is horziontal
		else if (image.getWidth(null) > image.getHeight(null)) {
			// Use 'fixedSide' for the new width, and calculate the 'newHeight'
			// to maintain the image's scale
			int newHeight = (fixedSide * image.getHeight(null))
					/ image.getWidth(null);
			bufferedImage = new BufferedImage(fixedSide, newHeight,
					BufferedImage.TYPE_INT_RGB);
			Graphics2D graphics2D = bufferedImage.createGraphics();
			graphics2D.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
					RenderingHints.VALUE_INTERPOLATION_BILINEAR);
			graphics2D.drawImage(image, 0, 0, fixedSide, newHeight, null);

		}
		// If your invoked image is vertical
		else if (image.getWidth(null) < image.getHeight(null)) {
			// Use 'fixedSide' for the new height, and calculate the 'newWidth'
			// to maintain the image's scale
			int newWidth = (fixedSide * image.getWidth(null))
					/ image.getHeight(null);
			bufferedImage = new BufferedImage(newWidth, fixedSide,
					BufferedImage.TYPE_INT_RGB);
			Graphics2D graphics2D = bufferedImage.createGraphics();
			graphics2D.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
					RenderingHints.VALUE_INTERPOLATION_BILINEAR);
			graphics2D.drawImage(image, 0, 0, newWidth, fixedSide, null);

		} else
			return null;

		// Save 'image' to ByteArrayOutputStream, and create a JPEGImageEncoder
		// to encode the 'bufferedImage'
		ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
		JPEGImageEncoder imageEncoder = JPEGCodec
				.createJPEGEncoder(outputStream);
		JPEGEncodeParam encodeParam = imageEncoder
				.getDefaultJPEGEncodeParam(bufferedImage);

		// Modify the resized image quality according the original one
		encodeParam.setQuality((float) quality / 100.0f, false);
		imageEncoder.setJPEGEncodeParam(encodeParam);
		imageEncoder.encode(bufferedImage);

		// resizedImage[] is the final result after all modifications
		byte[] resizedImage = outputStream.toByteArray();
		outputStream.close();

		return resizedImage;
	}

	/**
	 * Flips the supplied BufferedImage vertically. This is often a necessary
	 * conversion step to display a Java2D image correctly with OpenGL and vice
	 * versa.
	 */
	public static void flipImageVertically(BufferedImage image) {
		WritableRaster raster = image.getRaster();
		Object scanline1 = null;
		Object scanline2 = null;

		for (int i = 0; i < image.getHeight() / 2; i++) {
			scanline1 = raster.getDataElements(0, i, image.getWidth(), 1,
					scanline1);
			scanline2 = raster.getDataElements(0, image.getHeight() - i - 1,
					image.getWidth(), 1, scanline2);
			raster.setDataElements(0, i, image.getWidth(), 1, scanline2);
			raster.setDataElements(0, image.getHeight() - i - 1,
					image.getWidth(), 1, scanline1);
		}
	}

	/**
	 * Creates a <code>BufferedImage</code> with a pixel format compatible with
	 * the graphics environment. The returned image can thus benefit from
	 * hardware accelerated operations in Java2D API.
	 * 
	 * @param width
	 *            The width of the image to be created
	 * @param height
	 *            The height of the image to be created
	 * 
	 * @return A instance of <code>BufferedImage</code> with a type compatible
	 *         with the graphics card.
	 */
	public static BufferedImage createCompatibleImage(int width, int height) {
		GraphicsConfiguration configuration = GraphicsEnvironment
				.getLocalGraphicsEnvironment().getDefaultScreenDevice()
				.getDefaultConfiguration();
		return configuration.createCompatibleImage(width, height);
	}

	/**
	 * Creates a thumbnail from an image. A thumbnail is a scaled down version
	 * of the original picture. This method will retain the width to height
	 * ratio of the original picture and return a new instance of
	 * <code>BufferedImage</code>. The original picture is not modified.
	 * 
	 * @param image
	 *            The original image to sample down
	 * @param thumbWidth
	 *            The width of the thumbnail to be created
	 * 
	 * @throws IllegalArgumentException
	 *             If thumbWidth is greater than image.getWidth()
	 * 
	 * @return A thumbnail with the requested width or the original picture if
	 *         thumbWidth = image.getWidth()
	 */
	public static BufferedImage createThumbnail(BufferedImage image,
			int thumbWidth) {
		// Thanks to Romain Guy for this utility
		if (thumbWidth > image.getWidth()) {
			throw new IllegalArgumentException(
					"Thumbnail width must be greater than image width");
		}

		if (thumbWidth == image.getWidth()) {
			return image;
		}

		float ratio = (float) image.getWidth() / (float) image.getHeight();
		int width = image.getWidth();
		BufferedImage thumb = image;

		do {
			width /= 2;
			if (width < thumbWidth) {
				width = thumbWidth;
			}

			BufferedImage temp = createCompatibleImage(width,
					(int) (width / ratio));
			Graphics2D g2 = temp.createGraphics();
			g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
					RenderingHints.VALUE_INTERPOLATION_BILINEAR);
			g2.drawImage(thumb, 0, 0, temp.getWidth(), temp.getHeight(), null);
			g2.dispose();
			thumb = temp;
		} while (width != thumbWidth);

		return thumb;
	}

	public static BufferedImage scaleImage(BufferedImage src, int width,
			int height) {
		Image scaled = src.getScaledInstance(width, height, 0);
		BufferedImage ret = null;
		/*
		 * ColorModel cm = src.getColorModel(); if (cm instanceof
		 * IndexColorModel) { ret = new BufferedImage( width, height,
		 * src.getType(), (IndexColorModel) cm ); } else { ret = new
		 * BufferedImage( src.getWidth(), src.getHeight(), src.getType() ); }
		 * Graphics2D g = ret.createGraphics(); //clear alpha channel Composite
		 * comp = g.getComposite();
		 * g.setComposite(AlphaComposite.getInstance(AlphaComposite.CLEAR,
		 * 0.0f)); Rectangle2D.Double d = new
		 * Rectangle2D.Double(0,0,ret.getWidth(),ret.getHeight()); g.fill(d);
		 * g.setComposite(comp);
		 */
		ret = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
		Graphics2D g = ret.createGraphics();
		// copy image
		g.drawImage(scaled, 0, 0, null);
		return ret;
	}
	
	public static BufferedImage convertToRgb(BufferedImage bufImage) 
	{
		//BufferedImage bufImage = inImg.getAsBufferedImage();
		ICC_Profile iccProfile = ICC_Profile.getInstance(ICC_ColorSpace.CS_sRGB);
		ColorSpace cSpace = new ICC_ColorSpace(iccProfile);
		ColorConvertOp op = new ColorConvertOp(bufImage.getColorModel().getColorSpace(), cSpace, null);
		BufferedImage newImage = new BufferedImage(bufImage.getWidth(),	bufImage.getHeight(), BufferedImage.OPAQUE);
		op.filter(bufImage, newImage);

		return newImage;
	}

	public static java.awt.image.BufferedImage resizeImage(java.awt.Image image, int newWidth)
	{
		Image resizedImage = null; 

		int iWidth = image.getWidth(null);
		int iHeight = image.getHeight(null); 

		iWidth = image.getWidth(null);

		if (iWidth > iHeight) 
		{ 
			//resizedImage = image.getScaledInstance(newWidth, (newWidth * iHeight) / iWidth, Image.SCALE_SMOOTH);
			resizedImage = image.getScaledInstance(1024, 768, Image.SCALE_SMOOTH);
		} 
		else 
		{ 
			//resizedImage = image.getScaledInstance((newWidth * iWidth) / iHeight, newWidth, Image.SCALE_SMOOTH);
			resizedImage = image.getScaledInstance(768, 1024, Image.SCALE_SMOOTH);
		} 

//		 This code ensures that all the pixels in the image are loaded. 
		Image temp = new ImageIcon(resizedImage).getImage();
		
//		 Create the buffered image. 
		BufferedImage bufferedImage = new BufferedImage(temp.getWidth(null), temp.getHeight(null), 
		BufferedImage.TYPE_INT_RGB); 

//		 Copy image to buffered image. 
		Graphics g = bufferedImage.createGraphics(); 

//		 Clear background and paint the image. 
		g.setColor(Color.white); 
		g.fillRect(0, 0, temp.getWidth(null), temp.getHeight(null)); 
		g.drawImage(temp, 0, 0, null); 
		g.dispose(); 

//		 Soften. 
		float softenFactor = 0.05f; 
		float[] softenArray = {0, softenFactor, 0, softenFactor, 1-(softenFactor*4), softenFactor, 0, softenFactor, 0}; 
		Kernel kernel = new Kernel(3, 3, softenArray); 
		ConvolveOp cOp = new ConvolveOp(kernel, ConvolveOp.EDGE_NO_OP, null); 
		bufferedImage = cOp.filter(bufferedImage, null); 



		return bufferedImage;
	}
	
	/**
	 * Resize image based on max image file size.
	 * 
	 * @return  Int array where 0 = x, and 1 = y.
	 */
	public static int[] calculateResize(int width, int height, long imageByteSize, long maxImageSize)
	{
		int size[] = new int[2];
		int x = 0;
		int y = 0;
		
		float pct = (100 - (((float)maxImageSize / imageByteSize) * 100)) * (float).01;
		
		x = width - (int)(width * pct);
		y = height - (int)(height * pct);
		
		size[0] = x;
		size[1] = y;

		return size;
	}
	
//	 This method returns a buffered image with the contents of an image
	public static BufferedImage toBufferedImage(java.awt.Image image) 
	{
		long imageW = 0;
		long imageH = 0;

		
		if(image != null)
		{
			// call first time to populate image details in image object
			imageW = image.getWidth(null);
			try
			{
			Thread.sleep(500);
			}
			catch(Exception t)
			{}
			
			imageW = image.getWidth(null);
			imageH = image.getHeight(null);
			
			System.out.println(imageW + " x " + imageH);
		}

	    if (image instanceof BufferedImage) {
	        return (BufferedImage)image;
	    }

	    // This code ensures that all the pixels in the image are loaded
	    image = new ImageIcon(image).getImage();

	    // Determine if the image has transparent pixels; for this method's
	    // implementation, see Determining If an Image Has Transparent Pixels
	    boolean hasAlpha = hasAlpha(image);

	    // Create a buffered image with a format that's compatible with the screen
	    BufferedImage bimage = null;
	    GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
	    try {
	        // Determine the type of transparency of the new buffered image
	        int transparency = Transparency.OPAQUE;
	        if (hasAlpha) {
	            transparency = Transparency.BITMASK;
	        }

	        // Create the buffered image
	        GraphicsDevice gs = ge.getDefaultScreenDevice();
	        GraphicsConfiguration gc = gs.getDefaultConfiguration();
	        
	        bimage = gc.createCompatibleImage((int)imageW, (int)imageH, transparency);
	        //bimage = createCompatibleImage(image, gc, (int)imageW, (int)imageH);
	        
	    } catch (HeadlessException e) {
	        // The system does not have a screen
	    }

	    if (bimage == null) 
	    {
	        // Create a buffered image using the default color model
	        int type = BufferedImage.TYPE_INT_RGB;
	        if (hasAlpha) {
	            type = BufferedImage.TYPE_INT_ARGB;
	        }
	        bimage = new BufferedImage((int)imageW, (int)imageH, type);
	    }

	    // Copy image to buffered image
	    Graphics g = bimage.createGraphics();

	    // Paint the image onto the buffered image
	    g.drawImage(image, 0, 0, null);
	    g.dispose();

	    return bimage;
	}

	//	 This method returns true if the specified image has transparent pixels
	public static boolean hasAlpha(Image image) 
	{
	    // If buffered image, the color model is readily available
	    if (image instanceof BufferedImage) {
	        BufferedImage bimage = (BufferedImage)image;
	        return bimage.getColorModel().hasAlpha();
	    }

	    // Use a pixel grabber to retrieve the image's color model;
	    // grabbing a single pixel is usually sufficient
	     PixelGrabber pg = new PixelGrabber(image, 0, 0, 1, 1, false);
	    try {
	        pg.grabPixels();
	    } catch (InterruptedException e) {
	    }

	    // Get the image's color model
	    ColorModel cm = pg.getColorModel();
	    return cm.hasAlpha();
	}

	private static BufferedImage createCompatibleImage(java.awt.Image image,GraphicsConfiguration gc,int width, int height) 
	{ 
		//GraphicsConfiguration gc = BufferedImageGraphicsConfig.getConfig(image);

		BufferedImage result = gc.createCompatibleImage(width, height, Transparency.TRANSLUCENT);
		Graphics2D g2 = result.createGraphics();
		//g2.drawRenderedImage(image, null);
		g2.drawImage(image, width, height, null);
		g2.dispose();
		return result; 
	}

	private static BufferedImage resize(BufferedImage image, int width, int height) 
	{ 
		int type = image.getType() == 0? BufferedImage.TYPE_INT_ARGB : image.getType();
		BufferedImage resizedImage = new BufferedImage(width, height, type);
		Graphics2D g = resizedImage.createGraphics();
		g.setComposite(AlphaComposite.Src);

		g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, 
		RenderingHints.VALUE_INTERPOLATION_BILINEAR);


		g.setRenderingHint(RenderingHints.KEY_RENDERING, 
		RenderingHints.VALUE_RENDER_QUALITY);


		g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, 
		RenderingHints.VALUE_ANTIALIAS_ON);

		g.drawImage(image, 0, 0, width, height, null);
		g.dispose();
		return resizedImage; 
	} 

	public static BufferedImage blurImage(BufferedImage image) 
	{ 
		float ninth = 1.0f/9.0f;
		float[] blurKernel = { 
		ninth, ninth, ninth,
		ninth, ninth, ninth,
		ninth, ninth, ninth

		};

		Map<RenderingHints.Key, Object> map = new HashMap<RenderingHints.Key, Object>();

		map.put(RenderingHints.KEY_INTERPOLATION, 
		RenderingHints.VALUE_INTERPOLATION_BILINEAR);


		map.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);

		map.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

		RenderingHints hints = new RenderingHints(map);
		BufferedImageOp op = new ConvolveOp(new Kernel(3, 3, blurKernel), ConvolveOp.EDGE_NO_OP, hints);
		return op.filter(image, null);

	}

	public static BufferedImage resizeTrick(BufferedImage image, int width, int height) 
	{ 
		//image = createCompatibleImage(image);
		//image = resize(image, 800, 600);

		image = resize(image, width, height);
		//image = blurImage(image);
		
		return image; 
	}
	
	
	


}